<template>
  <div class="fx-slider" :class="[size,{disabled}]" :style="sliderStyle">
    <div class="slider-line" :class="{'click-animation':!dragStart,'input-number':showInput}"
         ref="sliderLineDom" @click="sliderLineClick">
      <div class="slider-inline" :style="{width:`${sliderValue}%`}"></div>
      <div class="slider-dot" :class="{active:dragStart}" ref="sliderDotDom" :style="{left:`${sliderValue}%`}">
        <span v-if="showTooltip" v-tooltip="tooltipValue"/>
      </div>
      <ul class="step-dot" v-if="showStep&&step">
        <li v-for="(item,index) in Math.floor(100/step)" :key="item"
            v-tooltip="item*step" :style="{left:`${100/(100/step)*index}%`}"></li>
      </ul>
    </div>
    <fx-input-number v-if="showInput" v-model="sliderValue" :size="size" width="120" :min="0" :max="100"/>
  </div>
</template>

<script lang="ts">
import { computed, defineComponent, onMounted, onUnmounted, PropType, reactive, ref } from 'vue'
import { useModelWatch } from '@/base/hooks'
import { codeRule, ComponentSize, componentSizeArr } from '@/base/types'
import inputNumber from '../input/inputNumber.vue'

interface SliderProps {
  modelValue: number;
  size: ComponentSize;
  disabled: boolean;
  showTooltip: boolean;
  format: (val: number) => string;
  step: number;
  showStep: boolean;
  showInput: boolean;
  width: number | string;
}

export default defineComponent({
  name: 'FSlider',
  components: {
    fxInputNumber: inputNumber
  },
  props: {
    // v-model
    modelValue: {
      type: Number as PropType<number>,
      required: true
    },
    // 大小
    size: {
      type: String as PropType<ComponentSize>,
      default: 'default',
      validator (val: ComponentSize): boolean {
        return componentSizeArr.includes(val)
      }
    },
    // 禁用
    disabled: {
      type: Boolean as PropType<boolean>,
      default: false
    },
    // 显示提示
    showTooltip: {
      type: Boolean as PropType<boolean>,
      default: true
    },
    // 格式化
    format: {
      type: Function as PropType<() => string>,
      default: null
    },
    // 步数
    step: {
      type: Number as PropType<number>,
      default: 0
    },
    // 显示步数
    showStep: {
      type: Boolean as PropType<boolean>,
      default: false
    },
    // 显示输入框
    showInput: {
      type: Boolean as PropType<boolean>,
      default: false
    },
    // 宽度
    width: {
      type: [Number, String] as PropType<number | string>,
      default: null
    }
  },
  setup (props: SliderProps) {
    // 当前滑块值
    const sliderValue = ref<number>(props.modelValue)
    useModelWatch<number>(() => props.modelValue, sliderValue)
    // 滑块线DOM
    const sliderLineDom = ref<HTMLElement | undefined>(undefined)
    // 滑块线DOM
    const sliderDotDom = ref<HTMLElement | undefined>(undefined)
    // 提示显示
    const tooltipValue = computed(() => {
      if (!props.showTooltip) return ''
      const value = Math.floor(sliderValue.value)
      if (props.format && typeof props.format === 'function') {
        return props.format(value)
      }
      return value
    })
    const sliderStyle = computed(() => {
      const isCode = codeRule.test(String(props.width))
      return {
        width: isCode ? props.width : `${props.width}px`
      }
    })
    const disStart = reactive<{ left: number, oldLeft: number }>({
      left: 0,
      oldLeft: 0
    })
    const dragStart = ref<boolean>(false)

    // 点击滑块
    const sliderLineClick = (ev: MouseEvent): void => {
      if (!sliderLineDom.value || props.disabled) return
      const rect = sliderLineDom.value.getBoundingClientRect()
      const dis = (ev.x - rect.x) / rect.width * 100
      if (props.step) {
        const count = ((dis % props.step) + 1) >= (props.step / 2) ? props.step : 0
        sliderValue.value = Math.floor(dis / props.step) * props.step + count
      } else {
        sliderValue.value = dis
      }
    }
    const handleMousedown = (ev: MouseEvent): void => {
      ev.stopPropagation()
      ev.preventDefault()
      if (props.disabled) return
      dragStart.value = true
      disStart.left = ev.clientX
      disStart.oldLeft = sliderValue.value
      document.addEventListener('mousemove', handleMousemove)
      document.addEventListener('mouseup', handleMouseup)
    }
    const handleMousemove = (ev: MouseEvent): void => {
      ev.stopPropagation()
      ev.preventDefault()
      if (!sliderDotDom.value || !sliderLineDom.value || !dragStart.value) return
      let disLeft = (ev.clientX - disStart.left) / sliderLineDom.value.clientWidth * 100 + disStart.oldLeft
      if (disLeft <= 0) disLeft = 0
      if (disLeft >= 100) disLeft = 100
      if (props.step) {
        if (((disLeft % props.step) + 1) >= (props.step / 2)) {
          sliderValue.value = Math.floor(disLeft / props.step) * props.step + props.step
        } else {
          sliderValue.value = Math.floor(disLeft / props.step) * props.step
        }
      } else {
        sliderValue.value = Math.floor(disLeft)
      }
    }
    const handleMouseup = (ev: MouseEvent): void => {
      ev.stopPropagation()
      ev.preventDefault()
      dragStart.value = false
      document.removeEventListener('mousemove', handleMousemove)
      document.removeEventListener('mouseleave', handleMouseup)
    }

    onMounted(() => {
      if (!sliderDotDom.value) return
      sliderDotDom.value.addEventListener('mousedown', handleMousedown)
    })
    onUnmounted(() => {
      if (!sliderDotDom.value) return
      sliderDotDom.value.removeEventListener('mousedown', handleMousedown)
    })

    return {
      dragStart,
      sliderStyle,
      sliderValue,
      tooltipValue,
      sliderDotDom,
      sliderLineDom,
      sliderLineClick
    }
  }
})
</script>
